package com.lch.designpattern.proxy.dynamicproxy.gpproxy;

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * 手写动态代理实现
 *
 * @author: liuchenhui
 * @create: 2019-04-18 22:42
 **/

/**
 * 用来生成原代码的生成类，相当于一个工具类
 */
public class GPProxy {
    public static final String HH = "\n";

    public static Object newProxyInstance(GPClassLoader loader,
                                          Class<?>[] interfaces,
                                          GPInvocationHandler h) {

        try {
            // 1、动态生成源代码.java文件
            String src = generateSrc(interfaces);

            // 2、java文件输出到磁盘
            String filePath = GPProxy.class.getResource("").getPath();
            File file = new File(filePath + "$GPProxy0.java");
            FileWriter fw = new FileWriter(file);
            fw.write(src);
            fw.flush();
            fw.close();

            // 3、把生成的.java文件编译成.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> iterable = manager.getJavaFileObjects(file);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();

            // 4、编译生成的.class文件加载到jvm中来
            Class calzz = loader.findClass("$GPProxy0");
            Constructor constructor = calzz.getConstructor(GPInvocationHandler.class);
            file.delete();

            // 5、返回字节码重组以后的新的代理对象
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("package com.lch.designpattern.proxy.dynamicproxy.gpproxy;" + HH);
        buffer.append("import com.lch.designpattern.proxy.Person;" + HH);
        buffer.append("import java.lang.reflect.*;" + HH);
        // 类
        buffer.append("public final class $GPProxy0 implements " + interfaces[0].getSimpleName() + "{" + HH);
        // 构造函数
        buffer.append("private GPInvocationHandler handler;" + HH);
        buffer.append("public $GPProxy0 (GPInvocationHandler handler) { this.handler = handler; }" + HH);
        // 方法
        for (Method method : interfaces[0].getMethods()) {
            String methodName = method.getName();
            Class<?>[] parameterTypes = method.getParameterTypes();
            StringBuilder args = new StringBuilder();
            StringBuilder involeargs = new StringBuilder();
            StringBuilder classArgs = new StringBuilder();
            if (parameterTypes.length != 0) {
                for (int i = 0; i < parameterTypes.length; i++) {
                    classArgs.append(parameterTypes[i].getSimpleName());
                    args.append(parameterTypes[i].getSimpleName()).append(" arg").append(i);
                    involeargs.append(involeargs).append("arg").append(i);
                }
            }
            involeargs = new StringBuilder("new Object[]{" + involeargs + "}");
            classArgs = classArgs.length() == 0 ? classArgs : classArgs.append(".class");
            buffer.append("@Override" + HH);
            buffer.append("public " + method.getReturnType().getSimpleName() + " " + methodName + "(" + args + ") {" + HH);
            buffer.append("try{" + HH);
            buffer.append("Method method =" + interfaces[0].getName() + ".class.getMethod(\"" + method.getName() + "\", new Class[]{" + classArgs + "});" + HH);
            if (!"void".equals(method.getReturnType().getSimpleName())) {
                buffer.append("return (" + method.getReturnType().getSimpleName() + ")");
            }
            buffer.append("this.handler.invoke(this, method, " + involeargs + ");" + HH);
            buffer.append("} catch(Throwable throwable) {" + HH);
            buffer.append("throwable.printStackTrace();" + HH);
            buffer.append("}" + HH);
            if (!"void".equals(method.getReturnType().getSimpleName())) {
                buffer.append("return null;");
            }
            buffer.append("}" + HH);
        }
        buffer.append("}" + HH);
        return buffer.toString();
    }


}
